// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/streams/stream_url_request_job.h"

#include "base/memory/ptr_util.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/test/test_simple_task_runner.h"
#include "content/browser/streams/stream.h"
#include "content/browser/streams/stream_registry.h"
#include "content/browser/streams/stream_write_observer.h"
#include "net/base/request_priority.h"
#include "net/http/http_byte_range.h"
#include "net/http/http_response_headers.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_job_factory_impl.h"
#include "net/url_request/url_request_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace content {

namespace {

    const int kBufferSize = 1024;
    const char kTestData1[] = "Hello";
    const char kTestData2[] = "Here it is data.";

    const GURL kStreamURL("blob://stream");

} // namespace

class StreamURLRequestJobTest : public testing::Test {
public:
    // A simple ProtocolHandler implementation to create StreamURLRequestJob.
    class MockProtocolHandler : public net::URLRequestJobFactory::ProtocolHandler {
    public:
        MockProtocolHandler(StreamRegistry* registry)
            : registry_(registry)
        {
        }

        // net::URLRequestJobFactory::ProtocolHandler override.
        net::URLRequestJob* MaybeCreateJob(
            net::URLRequest* request,
            net::NetworkDelegate* network_delegate) const override
        {
            scoped_refptr<Stream> stream = registry_->GetStream(request->url());
            if (stream.get())
                return new StreamURLRequestJob(request, network_delegate, stream);
            return NULL;
        }

    private:
        StreamRegistry* registry_;
    };

    StreamURLRequestJobTest() { }

    void SetUp() override
    {
        registry_.reset(new StreamRegistry());

        url_request_job_factory_.SetProtocolHandler(
            "blob", base::MakeUnique<MockProtocolHandler>(registry_.get()));
        url_request_context_.set_job_factory(&url_request_job_factory_);
    }

    void TearDown() override { }

    void TestSuccessRequest(const GURL& url,
        const std::string& expected_response)
    {
        TestRequest("GET", url, net::HttpRequestHeaders(), 200, net::OK,
            expected_response);
    }

    void TestRequest(const std::string& method,
        const GURL& url,
        const net::HttpRequestHeaders& extra_headers,
        int expected_status_code,
        int expected_error_code,
        const std::string& expected_response)
    {
        net::TestDelegate delegate;
        request_ = url_request_context_.CreateRequest(
            url, net::DEFAULT_PRIORITY, &delegate);
        request_->set_method(method);
        if (!extra_headers.IsEmpty())
            request_->SetExtraRequestHeaders(extra_headers);
        request_->Start();

        base::RunLoop().RunUntilIdle();

        // Verify response.
        if (expected_error_code)
            EXPECT_EQ(expected_error_code, request_->status().error());
        else
            EXPECT_TRUE(request_->status().is_success());
        ASSERT_TRUE(request_->response_headers());
        EXPECT_EQ(expected_status_code,
            request_->response_headers()->response_code());
        EXPECT_EQ(expected_response, delegate.data_received());
    }

protected:
    base::MessageLoopForIO message_loop_;
    std::unique_ptr<StreamRegistry> registry_;

    net::URLRequestContext url_request_context_;
    net::URLRequestJobFactoryImpl url_request_job_factory_;
    std::unique_ptr<net::URLRequest> request_;
};

TEST_F(StreamURLRequestJobTest, TestGetSimpleDataRequest)
{
    scoped_refptr<Stream> stream(
        new Stream(registry_.get(), NULL, kStreamURL));

    scoped_refptr<net::StringIOBuffer> buffer(
        new net::StringIOBuffer(kTestData1));

    stream->AddData(buffer, buffer->size());
    stream->Finalize(net::OK);

    TestSuccessRequest(kStreamURL, kTestData1);
}

TEST_F(StreamURLRequestJobTest, TestGetLargeStreamRequest)
{
    scoped_refptr<Stream> stream(
        new Stream(registry_.get(), NULL, kStreamURL));

    std::string large_data;
    large_data.reserve(kBufferSize * 5);
    for (int i = 0; i < kBufferSize * 5; ++i)
        large_data.append(1, static_cast<char>(i % 256));

    scoped_refptr<net::StringIOBuffer> buffer(
        new net::StringIOBuffer(large_data));

    stream->AddData(buffer, buffer->size());
    stream->Finalize(net::OK);
    TestSuccessRequest(kStreamURL, large_data);
}

TEST_F(StreamURLRequestJobTest, TestGetNonExistentStreamRequest)
{
    net::TestDelegate delegate;
    request_ = url_request_context_.CreateRequest(
        kStreamURL, net::DEFAULT_PRIORITY, &delegate);
    request_->set_method("GET");
    request_->Start();

    base::RunLoop().RunUntilIdle();

    // Verify response.
    EXPECT_FALSE(request_->status().is_success());
}

TEST_F(StreamURLRequestJobTest, TestRangeDataRequest)
{
    scoped_refptr<Stream> stream(
        new Stream(registry_.get(), NULL, kStreamURL));

    scoped_refptr<net::StringIOBuffer> buffer(
        new net::StringIOBuffer(kTestData2));

    stream->AddData(buffer, buffer->size());
    stream->Finalize(net::OK);

    net::HttpRequestHeaders extra_headers;
    extra_headers.SetHeader(net::HttpRequestHeaders::kRange,
        net::HttpByteRange::Bounded(0, 3).GetHeaderValue());
    TestRequest("GET", kStreamURL, extra_headers,
        200, net::OK, std::string(kTestData2, 4));
}

TEST_F(StreamURLRequestJobTest, TestInvalidRangeDataRequest)
{
    scoped_refptr<Stream> stream(
        new Stream(registry_.get(), NULL, kStreamURL));

    scoped_refptr<net::StringIOBuffer> buffer(
        new net::StringIOBuffer(kTestData2));

    stream->AddData(buffer, buffer->size());
    stream->Finalize(net::OK);

    net::HttpRequestHeaders extra_headers;
    extra_headers.SetHeader(net::HttpRequestHeaders::kRange,
        net::HttpByteRange::Bounded(1, 3).GetHeaderValue());
    TestRequest("GET", kStreamURL, extra_headers, 405,
        net::ERR_METHOD_NOT_SUPPORTED, std::string());
}

} // namespace content
